<script>
import Thead from './Thead'
import THeadFix from './THeadFix'
import Tbody from './Tbody'
import TbodyFix from './TbodyFix'
import TableTemplate from './TableTemplate'
import FilterPopover from './FilterPopover'
import { getTableWidth } from './util'

export default {
  name: 'Table',

  components: { Thead, THeadFix, Tbody, TbodyFix, TableTemplate, FilterPopover },

  provide() {
    return {
      handlerClickRow: this.handlerClickRow,
      handlerClickTree: this.handlerClickTree,
      cellStyle: this.cellStyle
    }
  },

  props: {
    table: {
      type: Array,
      default: () => []
    },

    rowHeight: {
      type: Number,
      default: 26
    },

    // 每行数据的唯一值
    rowKey: {
      type: String,
      default: 'id',
      require: true
    },

    // 存储树形结构子集的字段
    childrenAlisas: {
      type: String,
      default: ''
    },

    headerCellStyle: {
      type: Function
    },

    rowStyle: {
      type: Function
    },

    cellStyle: {
      type: Function
    },

    treeIcon: {
      default: 'arrow',
      type: String
    },

    filterable: {
      type: Boolean,
      default: false
    },

    cb: {
      type: Function
    },

    cbArgs: {
      type: String,
      default: ''
    },

    rowClick: {
      type: Function,
      default: null
    }
  },

  data() {
    return {
      // 所有列的最大depth
      maxDepth: 0,
      // 每一列的最大depth
      colDepthData: {},
      // 最大的td层级
      // 基本结构
      structure: [],
      // 底层数据渲染的真实数据（包括slot）
      bottomList: [],
      // 计算底层的td的宽度之后得到的初始table宽度
      initTableWidth: 0,
      // 局部渲染的数据
      visibleList: [],
      // 滚动条距离左边距离
      leftDistance: 0,

      // 整个容器的宽度
      containerWidth: 0,
      // 整个容器的高度
      containerHeight: 0,
      // T-Head 高度
      tHeadHeight: 0,
      // 处理的数据
      processedList: [],
      // 记录每个行数据的打开和关闭状态
      selectState: {},
      // 排序相关信息
      sortInfo: {
        selectSortTd: {},
        type: 'default'
      },
      // 是否显示过滤弹窗
      isFilterPopover: false,

      // 过滤选中的列信息
      currentfilterData: {},

      // 过滤条件池
      filterPool: [],

      // 当前被选中
      currentSelectId: ''
    }
  },

  computed: {
    // 是否滚动到最右侧
    isFarRight() {
      const body = this.$refs.body
      return !(this.leftDistance + body.clientWidth !== body.scrollWidth)
    },
    isTreeType() {
      return this.childrenAlisas !== ''
    },

    leftFixData() {
      return this.bottomList.filter(item => item.fix === 'left')
    },

    rightFixData() {
      return this.bottomList.filter(item => item.fix === 'right')
    },

    // table 宽度
    tableWidth() {
      return this.initTableWidth > this.containerWidth ? this.initTableWidth : this.containerWidth
    },

    tableList: {
      // 数据是树型的,且全部展开
      set(row) {
        const copyTable = JSON.parse(JSON.stringify(this.table))

        const filterList = this.filterData(this.filterPool, copyTable)
        const sortedList = this.sortData(this.sortInfo.selectSortTd, filterList)
        if (this.isTreeType) {
          if (row) {
            this.processedList = this.openTreeFilter(sortedList, [], row, 0)
          } else {
            this.processedList = this.spreadTreeList(sortedList, [], 0)
          }
        } else {
          // index: 原数组索引
          this.processedList = sortedList.map((item, index) => {
            return {
              id: item[this.rowKey],
              index: index,
              data: item
            }
          })
        }
      },
      get() {
        return this.processedList
      }
    },

    bodyHeight() {
      // 大概高度
      const maxHeight = this.containerHeight - this.tHeadHeight
      // 大概高度所能容纳的行数
      const rowCount = Math.floor(maxHeight / this.rowHeight)
      // 是否存在底部滚动条
      const discussHeight = this.isBottomScroll
        ? rowCount * this.rowHeight + 16
        : rowCount * this.rowHeight
      // 最终的高度
      return discussHeight > maxHeight ? discussHeight - this.rowHeight : discussHeight
    },

    totalHeight() {
      // if (window.devicePixelRatio > 1) {
      //   return (this.tableList.length + 1) * this.rowHeight
      // } else {
      //   return this.tableList.length * this.rowHeight
      // }
      return (this.tableList.length + 1) * this.rowHeight
    },

    // 底部是否有滚动条
    isBottomScroll() {
      return this.tableWidth >= this.containerWidth
    },

    // table 自动宽度
    isNaturalTableWidth() {
      return this.tableWidth === this.containerWidth
    }
  },

  watch: {
    table: {
      handler() {
        this.handlerClickTree('')
      },
      immediate: true
    }
  },

  created() {
    window.addEventListener('resize', this.handlerResize)

    this.$nextTick(() => {
      this.getContainerRect()
    })
  },

  beforeDestroy() {
    window.removeEventListener('resize', this.handlerResize)
  },

  methods: {
    handlerClickRow(id, row, index) {
      this.currentSelectId = id
      this.rowClick && this.rowClick(id, row, index)
    },

    setDefaultFilter() {
      if (this.initFilter) return
      this.filterPool = this.bottomList.map(item => {
        return {
          currentTd: item,
          inputVal: '',
          symbolData: {
            type: 'string',
            label: '开头是(I)',
            symbol: 'I'
          }
        }
      })
      this.initFilter = true
    },

    commit({ maxDepth, structure, bottomList, tableWidth }) {
      this.maxDepth = maxDepth
      this.structure = structure
      this.initTableWidth = tableWidth
      this.bottomList = bottomList
      this.setDefaultFilter()
    },

    showFilterPopover({ rect, currentTd }) {
      this.isFilterPopover = true

      let top = 0
      let left = 0

      // 当前元素相对其父元素的top、left
      const parentPosition = this.$el.getBoundingClientRect()
      const relativeTop = rect.bottom - parentPosition.top
      const relativeLeft = rect.left - parentPosition.left

      const rightMax = rect.left + 220

      top = relativeTop

      if (rightMax > parentPosition.right) {
        left = relativeLeft - (rightMax - parentPosition.right)
      } else {
        left = relativeLeft
      }

      const heightMax = parentPosition.bottom - rect.bottom

      const height = heightMax > 440 ? 440 : heightMax - 30

      const position = {
        top: top + 'px',
        left: left + 'px',
        height: height + 'px',
        overflowY: height < 440 ? 'scroll' : 'auto'
      }

      this.currentfilterData = { position, currentTd }
    },

    updateFilterPool({ eventType = '', inputVal = '', symbolData = {}, currentTd }) {
      this.isFilterPopover = false

      const index = this.filterPool.findIndex(poolItem => currentTd.id === poolItem.currentTd.id)

      const isExist = index !== -1

      if (eventType === 'symbol') {
        this.currentfilterData = { ...this.currentfilterData, symbolData, inputVal: '' }
      } else if (eventType === 'reset') {
        this.currentfilterData = { ...this.currentfilterData, symbolData: {}, inputVal: '' }
      } else {
        this.currentfilterData = {
          symbolData: isExist ? this.filterPool[index].symbolData : {},
          inputVal,
          currentTd
        }
      }

      // pool中存在
      if (isExist) {
        this.filterPool[index] = { ...this.currentfilterData }
      } else {
        this.filterPool.push({ ...this.currentfilterData })
      }

      this.filterPool = [...this.filterPool]
      if ((eventType == 'symbol' && symbolData.symbol == 'B') || symbolData.symbol == 'C') {
        return this.handlerClickTree('')
      }
      if (eventType == 'symbol') return

      switch (eventType) {
        case 'reset':
          this.handlerClickTree('')
          break
        case '':
          this.handlerClickTree('')
          break
        default:
          return
      }
    },

    updateLinePosition({ left, tdId, show, distance }) {
      const line = this.$refs.line
      line.style.display = show
      if (show === 'none') {
        const index = this.bottomList.findIndex(item => item.id === tdId)
        this.bottomList[index].width = distance
        // const curTotalWdith = this.bottomList.reduce((total, cur) => {
        //   return total + cur.width
        // }, 0)
        // 当前宽度若没有超过容器，则把除了当前index的其他列放大
        // if (curTotalWdith <= this.containerWidth) {
        //   const lastIndex = this.bottomList.length - 1
        //   if (lastIndex === index) {
        //     lastIndex
        //   }
        // }

        this.initTableWidth = getTableWidth(this.bottomList)
        return
      }
      line.style.left = left + 'px'
    },

    handlerResize() {
      this.getContainerRect()
      this.$nextTick(() => {
        this.updateVisibleList(this.$refs.body.scrollTop)
      })
    },

    /* 滚动条移动 */
    handlerScroll() {
      this.isFilterPopover = false
      const body = this.$refs.body
      this.leftDistance = body.scrollLeft

      // 滚动条防抖优化方法
      // console.log( this.processedList.length)
      // this.debounce(() => {
      // this.updateVisibleList(body.scrollTop)
      //   this.$refs.head.style.transform = `translate3d(${-this.leftDistance}px, 0, 0)`
      // }, 50)()
      this.updateVisibleList(body.scrollTop)
      this.$refs.head.style.transform = `translate3d(${-this.leftDistance}px, 0, 0)`
    },

    // 函数防抖
    debounce(func, wait) {
      return () => {
        this.timer && clearTimeout(this.timer)
        this.timer = setTimeout(func, wait)
      }
    },
    /* 修改可视区域的值 */
    updateVisibleList(topDistance) {
      this.$nextTick(() => {
        topDistance = topDistance || 0
        const visibleCount = Math.ceil(this.$refs.body.clientHeight / this.rowHeight)
        const start = Math.floor(topDistance / this.rowHeight)
        const end = start + visibleCount
        this.visibleList = this.tableList.slice(start, end)
      })
    },

    /* 鼠标滚动 */
    handlerMousewheel(e) {
      const deltaY = e.detail || e.deltaY
      const direction = deltaY > 0 ? 'up' : 'down'
      // 页面往上
      if (direction === 'up') {
        const body = this.$refs.body
        body.scrollBy(0, this.rowHeight * 3)
      } else {
        const body = this.$refs.body
        body.scrollBy(0, -this.rowHeight * 3)
      }
    },

    /* 获取整个table容器高度 */
    getContainerRect() {
      const tableRect = this.$el.getBoundingClientRect()
      this.containerWidth = tableRect.width
      this.containerHeight = tableRect.height
    },

    /* 获取t-head高度 */
    getTHeadHeight(height) {
      this.tHeadHeight = height
    },

    /* 将树形数据展开平铺 */
    spreadTreeList(list = [], result = [], parentLevel = 0) {
      for (let i = 0, len = list.length; i < len; i++) {
        const current = {
          id: list[i][this.rowKey],
          level: parentLevel + 1,
          index: i,
          data: list[i]
        }
        result.push(current)
        const children = current.data[this.childrenAlisas]
        if (children && children.length > 0) {
          current.hasChildren = true
          // 记录有子集的节点的打开关闭状态（默认是打开）
          current.isOpen = true
          const currentId = current.id
          this.selectState[currentId] = current.isOpen
          this.spreadTreeList(children, result, parentLevel + 1)
        }
      }
      return result
    },

    /* 过滤关闭状态的树节点 */
    openTreeFilter(list = [], result = [], currentSelectRow = {}, parentLevel = 0) {
      for (let i = 0, len = list.length; i < len; i++) {
        const current = {
          id: list[i][this.rowKey],
          level: parentLevel + 1,
          index: i,
          data: list[i]
        }
        result.push(current)
        const children = current.data[this.childrenAlisas]
        if (children && children.length > 0) {
          current.hasChildren = true
          const currentId = current.id
          const selectId = currentSelectRow.id
          if (currentId === selectId) {
            const state = !this.selectState[currentId]
            this.selectState[currentId] = state
            current.isOpen = state
            // 关闭状态就舍弃它下面的子节点
            if (!current.isOpen) {
              continue
            }
          } else {
            current.isOpen = this.selectState[currentId]
            // 关闭状态就舍弃它下面的子节点
            if (!current.isOpen) {
              continue
            }
          }
          // 当前用户选择是打开还是关闭
          this.openTreeFilter(children, result, currentSelectRow, parentLevel + 1)
        }
      }
      return result
    },

    /* 打开或者关闭树节点 */
    handlerClickTree(row) {
      this.tableList = row
      this.$nextTick(() => {
        this.updateVisibleList(this.$refs.body.scrollTop)
        // 只能返回一层的数据总数
        if (this.cb) {
          const argList = this.cbArgs.split(',')

          const objData = {}

          argList.forEach(arg => {
            const result = this.tableList.reduce((total, cur) => {
              return this.$calc(total, cur.data[arg], 'add', {
                precision: 7
              })
            }, 0)

            objData[arg] = result
          })

          this.cb({ totalLen: this.table.length, currentLen: this.tableList.length, ...objData })
        }
      })
    },

    /* 修改选中的sortId */
    updateSelectSortTd(currentId, type) {
      switch (type) {
        case 'ascending':
          this.sortInfo.type = 'descending'
          break
        case 'descending':
          this.sortInfo.type = 'default'
          break
        default:
          this.sortInfo.type = 'ascending'
      }
      this.sortInfo.selectSortTd = currentId
      this.handlerClickTree('')
    },

    filterData(poolList = [], list = []) {
      poolList.forEach(poolItem => {
        const symbol = poolItem.symbolData.symbol
        const inputVal = poolItem.inputVal
        const prop = poolItem.currentTd.prop

        if (symbol === 'C' || symbol === 'B') {
          list = this.filterBySymbol(prop, symbol, inputVal, list)
          return
        }
        if (String(inputVal) === '') return
        if (!symbol) return
        list = this.filterBySymbol(prop, symbol, inputVal, list)
      })
      return list
    },

    filterBySymbol(prop, symbol, inputVal, list = []) {
      return list.filter(item => {
        let isTrue = false
        switch (symbol) {
          // 等于
          case 'E':
            isTrue = new RegExp(`^${inputVal}$`, 'i').test(item[prop])
            break
          // 不等于
          case 'N':
            isTrue = !new RegExp(`^${inputVal}$`, 'i').test(item[prop])
            break
          // 大于
          case 'G':
            isTrue = item[prop] > inputVal
            break
          // 大于或等于
          case 'O':
            isTrue = item[prop] >= inputVal
            break
          // 小于
          case 'L':
            isTrue = item[prop] < inputVal
            break
          // 小于或等于
          case 'Q':
            isTrue = item[prop] <= inputVal
            break
          // 开头是
          case 'I':
            isTrue = new RegExp(`^${inputVal}`, 'i').test(item[prop])
            break
          // 结尾是
          case 'T':
            isTrue = new RegExp(`${inputVal}$`, 'i').test(item[prop])
            break
          // 包含
          case 'A':
            isTrue = new RegExp(`${inputVal}`, 'i').test(item[prop])
            break
          // 不包含
          case 'D':
            isTrue = !new RegExp(`${inputVal}`, 'i').test(item[prop])
            break
          // 为空
          case 'B':
            isTrue = item[prop] == null || String(item[prop]).length == 0
            break
          // 不为空
          case 'C':
            isTrue = item[prop] !== null && String(item[prop]).length > 0
            break
          default:
            isTrue = true
        }
        return isTrue
      })
    },

    sortInner(currentId = {}, list = []) {
      for (let i = 0, len = list.length; i < len; i++) {
        const current = list[i]
        if (current[this.childrenAlisas]) {
          this.sortByType(currentId, current[this.childrenAlisas])
          this.sortData(currentId, current[this.childrenAlisas])
        }
      }
    },

    sortData(currentId = {}, list = []) {
      if (this.sortInfo.type === 'default') return list
      /* 第一层不递归 */
      this.sortInner(currentId, list)
      /* 最后对第一层递归 */
      return this.sortByType(currentId, list)
    },

    /* 排序 */
    sortByType(currentId = {}, list = []) {
      if (this.sortInfo.type === 'default') return list
      const prop = currentId.prop
      switch (currentId.sort) {
        case 'date':
          list.sort((a, b) => {
            if (this.sortInfo.type === 'ascending') {
              return new Date(a[prop]) - new Date(b[prop])
            } else {
              return new Date(b[prop]) - new Date(a[prop])
            }
          })
          break
        case 'number':
          list.sort((a, b) => {
            if (this.sortInfo.type === 'ascending') {
              return a[prop] - b[prop]
            } else {
              return b[prop] - a[prop]
            }
          })
          break
        default:
          return list.sort((a, b) => {
            const aVal = a[prop] == void 0 ? '' : a[prop].toLowerCase().charCodeAt(0)
            const bVal = b[prop] == void 0 ? '' : b[prop].toLowerCase().charCodeAt(0)
            if (this.sortInfo.type === 'ascending') {
              return aVal - bVal
            } else {
              return bVal - aVal
            }
          })
      }
      return list
    }
  },

  render() {
    const isThead =
      this.structure.length > 0 ? (
        <Thead
          rowHeight={this.rowHeight}
          structure={this.structure}
          bottomList={this.bottomList}
          tableWidth={this.tableWidth}
          getTHeadHeight={this.getTHeadHeight}
          sortInfo={this.sortInfo}
          updateSelectSortTd={this.updateSelectSortTd}
          headerCellStyle={this.headerCellStyle}
          updateLinePosition={this.updateLinePosition}
          showFilterPopover={this.showFilterPopover}
          updateFilterPool={this.updateFilterPool}
          filterPool={this.filterPool}
          filterable={this.filterable}
        />
      ) : (
        ''
      )

    const isLeftTHeadFix =
      this.leftFixData.length > 0 && !this.isNaturalTableWidth ? (
        <THeadFix
          maxDepth={this.maxDepth}
          rowHeight={this.rowHeight}
          fixData={this.leftFixData}
          leftDistance={this.leftDistance}
          isFarRight={this.isFarRight}
          headerCellStyle={this.headerCellStyle}
          showFilterPopover={this.showFilterPopover}
          updateFilterPool={this.updateFilterPool}
          filterPool={this.filterPool}
          filterable={this.filterable}
          direction="left"
          key="left"
        />
      ) : (
        ''
      )

    const isRightTHeadFix =
      this.rightFixData.length > 0 && !this.isNaturalTableWidth ? (
        <THeadFix
          maxDepth={this.maxDepth}
          rowHeight={this.rowHeight}
          fixData={this.rightFixData}
          leftDistance={this.leftDistance}
          isFarRight={this.isFarRight}
          headerCellStyle={this.headerCellStyle}
          showFilterPopover={this.showFilterPopover}
          updateFilterPool={this.updateFilterPool}
          filterPool={this.filterPool}
          filterable={this.filterable}
          direction="right"
          key="right"
        />
      ) : (
        ''
      )

    const isTbody =
      this.structure.length > 0 ? (
        <Tbody
          currentSelectId={this.currentSelectId}
          isTreeType={this.isTreeType}
          bottomList={this.bottomList}
          tableWidth={this.tableWidth}
          visibleList={this.visibleList}
          rowHeight={this.rowHeight}
          leftDistance={this.leftDistance}
          tHeadHeight={this.tHeadHeight}
          bodyHeight={this.bodyHeight}
          isBottomScroll={this.isBottomScroll}
          handlerMousewheel={this.handlerMousewheel}
          rowStyle={this.rowStyle}
          treeIcon={this.treeIcon}
        />
      ) : (
        ''
      )

    let isLeftTbodyFix = ''

    let isRightTbodyFix = ''

    if (this.structure.length > 0) {
      isLeftTbodyFix =
        this.leftFixData.length > 0 && !this.isNaturalTableWidth ? (
          <TbodyFix
            currentSelectId={this.currentSelectId}
            isTreeType={this.isTreeType}
            fixData={this.leftFixData}
            visibleList={this.visibleList}
            rowHeight={this.rowHeight}
            tHeadHeight={this.tHeadHeight}
            leftDistance={this.leftDistance}
            isFarRight={this.isFarRight}
            bodyHeight={this.bodyHeight}
            isBottomScroll={this.isBottomScroll}
            handlerMousewheel={this.handlerMousewheel}
            rowStyle={this.rowStyle}
            treeIcon={this.treeIcon}
            direction="left"
            key="left"
          />
        ) : (
          ''
        )
      isRightTbodyFix =
        this.rightFixData.length > 0 && !this.isNaturalTableWidth ? (
          <TbodyFix
            currentSelectId={this.currentSelectId}
            isTreeType={this.isTreeType}
            fixData={this.rightFixData}
            visibleList={this.visibleList}
            rowHeight={this.rowHeight}
            tHeadHeight={this.tHeadHeight}
            leftDistance={this.leftDistance}
            isFarRight={this.isFarRight}
            bodyHeight={this.bodyHeight}
            isBottomScroll={this.isBottomScroll}
            handlerMousewheel={this.handlerMousewheel}
            rowStyle={this.rowStyle}
            direction="right"
            key="right"
          />
        ) : (
          ''
        )
    }

    const bodyStyle = {
      height: this.bodyHeight + 'px'
    }

    const bodyWrapStyle = {
      display: 'flex',
      alignItems: 'center',
      width: this.tableWidth + 'px',
      height: this.totalHeight ? this.totalHeight + 'px' : '100%'
    }

    const emptyStyle = {
      whiteSpace: 'nowrap',
      transform: `translateX(${this.containerWidth / 2}px)`
    }

    const lineStyle = {
      height:
        this.tHeadHeight + (this.isBottomScroll ? this.bodyHeight - 16 : this.bodyHeight) + 'px'
    }

    return (
      <div class="table">
        <TableTemplate commit={this.commit}>{this.$slots.default}</TableTemplate>

        <div class="head">
          <div ref="head">{isThead}</div>
          {isLeftTHeadFix}
          {isRightTHeadFix}
        </div>

        <div class="body" ref="body" style={bodyStyle} onScroll={this.handlerScroll}>
          <div style={bodyWrapStyle}>
            {this.tableList.length === 0 ? <div style={emptyStyle}>暂无数据</div> : ''}
          </div>
        </div>

        {isTbody}
        {isLeftTbodyFix}
        {isRightTbodyFix}

        <div class="line" ref="line" style={lineStyle}></div>

        {this.isFilterPopover ? (
          <FilterPopover
            filterPool={this.filterPool}
            currentfilterData={this.currentfilterData}
            updateFilterPool={this.updateFilterPool}
            cancel={() => (this.isFilterPopover = false)}
          />
        ) : (
          ''
        )}
      </div>
    )
  }
}
</script>
<style scoped>
.table {
  position: relative;
  overflow: hidden;
  height: 100%;
}

.head {
  width: calc(100% - 17px);
  position: relative;
  overflow: hidden;
}

.body {
  overflow-x: auto;
  overflow-y: scroll;
  position: relative;
}

.line {
  display: none;
  position: absolute;
  top: 0;
  width: 1px;
  z-index: 1;
  border-right: 1px dashed #cccccc;
}
</style>
